/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include <tools/list.h>
#include <tools/macros.h>
#include <core/atomic.h>
#include <core/dprintf.h>
#include <core/mutex.h>
#include <core/schedule.h>
#include <core/task.h>
#include <uapi/errors.h>
#include <uapi/ipcbuffer.h>

void mutex_init(mutex_t *mutex, int sema)
{
    assert(sema > 0);
    atomic_store(&mutex->lock, sema);
    list_init(&mutex->wait_list);
    if (sema != 1) {
        list_init(&mutex->ownerlist);
    }
    mutex->owner  = NULL;
    mutex->maxsem = sema;
    mutex_get(mutex);
}

void mutex_lock(mutex_t *mutex)
{
    if (mutex->maxsem != 1) {
        if (atomic_get(&mutex->lock) != 0) {
            list_append(&mutex->ownerlist, &current()->semlist);
            task_set_retcode(current(), 0);
        } else {
            tcb_get(current());
            schedule_detach(current());
            task_set_state(current(), TASK_BLOCKED);
            list_append(&mutex->wait_list, &current()->runlist);
        }
    } else if (atomic_compare_exchange(&mutex->lock, 1, 0) != 0) {
        mutex->owner = current();
        task_set_retcode(current(), 0);
    } else {
        tcb_get(current());
        schedule_detach(current());
        task_set_state(current(), TASK_BLOCKED);
        list_append(&mutex->wait_list, &current()->runlist);
    }
}

void mutex_unlock(mutex_t *mutex)
{
    if (mutex->maxsem != 1) {
        if (list_tryremove(&mutex->ownerlist, &current()->semlist) == 0) {
            dprintf("this semaphore is not own to the current thread\r\n");
            return;
        }
    } else {
        if (mutex->owner != current()) {
            dprintf("this mutex is not own to the current thread\r\n");
            return;
        }
    }

    tcb_t *wakeup;

    if (mutex->maxsem != 1) {
        if (atomic_put(&mutex->lock, mutex->maxsem) != 0) {
            if (!list_is_empty(&mutex->wait_list)) {
                atomic_get(&mutex->lock);
                wakeup = container_of(mutex->wait_list.next, tcb_t, runlist);
                list_remove(&wakeup->runlist);
                task_set_state(wakeup, TASK_READY);
                if (0 == tcb_put(wakeup)) {
                    return;
                }
                list_append(&mutex->ownerlist, &wakeup->semlist);
                task_set_retcode(wakeup, 0);
                schedule_attach(wakeup);
            }
            task_set_retcode(current(), 0);
        } else {
            dprintf("post a full semaphore\r\n");
        }
    } else if (atomic_compare_exchange(&mutex->lock, 0, 1) == 0) {
        if (!list_is_empty(&mutex->wait_list)) {
            atomic_compare_exchange(&mutex->lock, 1, 0);
            wakeup = container_of(mutex->wait_list.next, tcb_t, runlist);
            list_remove(&wakeup->runlist);
            task_set_state(wakeup, TASK_READY);
            if (0 == tcb_put(wakeup)) {
                mutex->owner = NULL;
                return;
            }
            mutex->owner = wakeup;
            task_set_retcode(wakeup, 0);
            schedule_attach(wakeup);
        } else {
            mutex->owner = NULL;
        }
        task_set_retcode(current(), 0);
    } else {
        dprintf("unlock an unlocked mutex\r\n");
    }
}

int mutex_trylock(mutex_t *mutex)
{
    if (mutex->maxsem != 1) {
        if (atomic_get(&mutex->lock) != 0) {
            list_append(&mutex->ownerlist, &current()->semlist);
            return ESUCCESS;
        } else {
            return -EMUTEXLOCKED;
        }
    } else if (atomic_compare_exchange(&mutex->lock, 1, 0) != 0) {
        mutex->owner = current();
        return ESUCCESS;
    } else {
        return -EMUTEXLOCKED;
    }
}

cptr_t mutex_owner(mutex_t *mutex)
{
    if (mutex->maxsem != 1) {
        dprintf("TODO Semaphore\n");
        return 1;
    } else {
        if (mutex->owner != NULL) {
            return mutex->owner->cptr;
        }
    }
    return 0;
}

unsigned long mutex_destroy(capability_t *cap, mutex_t *mutex)
{
    unsigned long res;

    if (mutex->maxsem == 0) {
        return 0;
    }

    if (atomic_compare_exchange(&mutex->lock, 1, 0) != 0) {
        mutex_put(mutex);
        res = cap_get_long_info(cap);
        return res;
    }

    return 0;
}
